/*
 * MIT License
 *
 * Copyright (c) 2020 wen.gu <454727014@qq.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

/***************************************************************************
 * Name: xsocket.cpp
 *
 * Purpose: wrapper socket operation API for linux platform
 *
 * Developer:
 *   wen.gu , 2023-07-12
 *
 * TODO:
 *
 ***************************************************************************/
#include "config.h"

#if defined(HAS_LINUX_UDS) && (HAS_LINUX_UDS)

#include "shmipc/core/uds_socket.h"

#include <sys/socket.h>
#include <sys/types.h>
#include <sys/un.h>
#include <unistd.h>
#include <cstring>
#include <errno.h>
#include <arpa/inet.h>

#define SOCK_SEND_TRY_MAX_CNT 10

namespace shmipc::core {

struct UdsSocketHandler {
    UdsSocketId sock_id;
    std::string url;
    UdsSocketHandler(UdsSocketId fd):sock_id(fd) { /**Todo something */  }
    ~UdsSocketHandler() {
        if (!url.empty()) {
            printf("[%s.%d]====>:unlink: %s\n", __FUNCTION__, __LINE__, url.c_str());
            unlink(url.c_str());
        }

        if (sock_id != INVALID_XSOCKET_ID) {
            shutdown(sock_id, SHUT_RDWR);
            ::close(sock_id); 
            sock_id = INVALID_XSOCKET_ID;               
        }    
    }
};


static int SelectRecvPoll(UdsSocketId sock_id, fd_set& rd_set, int32_t timeout_ms) {
    FD_ZERO(&rd_set);
    FD_SET(sock_id, &rd_set);
    struct timeval sel_timeout{0};
    struct timeval* tv_ptr = nullptr;
    if (timeout_ms >= 0) {
        sel_timeout.tv_sec = timeout_ms / 1000;
        sel_timeout.tv_usec = (timeout_ms == core::WaitType::NoWait) ? 1 : ((timeout_ms % 1000) * 1000); /** set 1 to avoid cpu loading increase */
        tv_ptr = &sel_timeout;
    }

    return select(sock_id + 1, &rd_set, nullptr, nullptr, tv_ptr);
}

/** for client side */
static core::XErrC UdsSocketConnect(UdsSocketHandler* xsh, const std::string& url) {
    //printf("[%s.%d]====>:\n", __FUNCTION__, __LINE__);      
    struct sockaddr_un client_addr;
    client_addr.sun_family = AF_UNIX;
    snprintf(client_addr.sun_path, sizeof(client_addr.sun_path), "%s.client.%d.%ld.%p", url.c_str(), getpid(), xsh->sock_id, xsh);
    unlink(client_addr.sun_path);
    if (::bind(xsh->sock_id, (struct sockaddr*)&client_addr, sizeof(client_addr)) < 0) {
        //printf ("bind client url failed(err: %s)\n", strerror(errno));
        return XErrC::BindFailed;
    }
    
    struct sockaddr_un server_addr;
    server_addr.sun_family = AF_UNIX;
    strcpy(server_addr.sun_path, url.c_str());

    if (::connect(xsh->sock_id, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
        //printf ("connect to url(%s) failed(err: %s)\n", url.c_str(), strerror(errno));
        unlink(client_addr.sun_path);
        return XErrC::ConnectFailed;
    }  
    xsh->url = client_addr.sun_path; 
    
    return XErrC::OK; 
}

/** for server side */
static core::XErrC UdsSocketBind(UdsSocketHandler* xsh, const std::string& url) {
    struct sockaddr_un server_addr;
    server_addr.sun_family = AF_UNIX;
    strcpy(server_addr.sun_path, url.c_str());

    unlink(server_addr.sun_path); //todo refine me??
    if (::bind(xsh->sock_id, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
        printf ("bind server url(%s) failed(err: %s)\n", url.c_str(), strerror(errno));
        return XErrC::BindFailed;
    }

    if (::listen(xsh->sock_id, 10) < 0) {//todo refine me?
        printf("server(%s) listen for connections from client failed(err:%s)\n", url.c_str(), strerror(errno));
        unlink(server_addr.sun_path);
        return XErrC::ListenFailed;        
    } 

    xsh->url = url;

    return XErrC::OK;
}

static XResult<uint32_t> StreamRecv(UdsSocketHandler* xsh, uint8_t* buf, uint32_t size, int32_t timeout_ms) {
    fd_set rd_set;
    while (SelectRecvPoll(xsh->sock_id, rd_set, timeout_ms) > 0) {
        if ((FD_ISSET(xsh->sock_id, &rd_set))) {
            int32_t ret = ::recv(xsh->sock_id, buf, size, 0);
            if (ret > 0) {
                return {ret};
            } else if (ret < 0) {
                if ((errno == EAGAIN) || (errno == EWOULDBLOCK) || (errno == EINTR)) {
                    continue;
                }
                XResult<uint32_t>::FromError(XErrC::Undefined);
            } else {
                return XResult<uint32_t>::FromError(XErrC::Disconnected);
            }
        } //todo, if waitforever, is need try again?
    }

    return XResult<uint32_t>::FromError(XErrC::TimeOut);
}

static XResultCode StreamSend(int sock_id, const uint8_t* buf, uint32_t size) {
    uint32_t send_pos = 0;
    uint32_t max_try_cnt = SOCK_SEND_TRY_MAX_CNT;
    while (send_pos < size) { /** todo, spilt message if too big */
        int32_t ret = ::send(sock_id, buf + send_pos, size - send_pos, 0);
        if (ret <= 0) {
            if (ret < 0) {
                if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) { 
                    return XResultCode::FromError(XErrC::Disconnected); /** todo, return right error code */ 
                }

                max_try_cnt--;
                if (max_try_cnt == 0) { 
                    return XResultCode::FromError(XErrC::Disconnected);  /** todo, return right error code */  
                }
                continue; //try again
            } else if (errno == EINTR){
                max_try_cnt = SOCK_SEND_TRY_MAX_CNT;
                continue;
            } else { return XResultCode::FromError(XErrC::Disconnected); }
        }
        max_try_cnt = SOCK_SEND_TRY_MAX_CNT;
        send_pos += ret;
    } 

    return { }; 
}

static UdsSocketHandler* CreateSockHandler() {
    UdsSocketId socket_fd = ::socket(AF_UNIX, SOCK_STREAM, 0);

    if (socket_fd < 0) {
        printf("open uds socket failed(%s)\n", strerror(errno));
        return nullptr;
    }

    UdsSocketHandler* xsh = new UdsSocketHandler(socket_fd);
    if (xsh) { return xsh; }
    ::close(socket_fd);
    return nullptr;
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

XResult<UdsSocketId> UdsSocketServerOpen(const std::string& url) {
    UdsSocketHandler* xsh = CreateSockHandler();

    if (!xsh) { return XResult<UdsSocketId>::FromError(XErrC::InsufficientReousces); }

    XErrC ret = UdsSocketBind(xsh, url);

    if (ret != XErrC::OK) {
        delete xsh;
        return XResult<UdsSocketId>::FromError(ret);
    }

    return reinterpret_cast<UdsSocketId>(xsh);
}

core::XResult<UdsSocketId> UdsSocketClientOpen(const std::string& url) {
    UdsSocketHandler* xsh = CreateSockHandler();

    if (!xsh) { return XResult<UdsSocketId>::FromError(XErrC::InsufficientReousces);  }

    XErrC ret = UdsSocketConnect(xsh, url);

    if (ret != XErrC::OK) {
        delete xsh;
        return XResult<UdsSocketId>::FromError(ret);
    }

    return reinterpret_cast<UdsSocketId>(xsh);
}

XResultCode UdsSocketClose(UdsSocketId sock_id) {
    UdsSocketHandler* xsh;
    if ((sock_id == INVALID_XSOCKET_ID) || !(xsh = reinterpret_cast<UdsSocketHandler*>(sock_id))) {
        return XResultCode::FromError(XErrC::InvalidArgument);
    } 

    delete xsh;

    return { };
}


/** for orainted-connection protocol(e.g. tcp, uds) */
XResult<UdsSocketId> UdsSocketAccept(UdsSocketId sock_id, int32_t timeout_ms /*= core::WaitType::WaitForever*/) { 
    UdsSocketHandler* xsh; 
   if ((sock_id == INVALID_XSOCKET_ID) || !(xsh = reinterpret_cast<UdsSocketHandler*>(sock_id))) {
        return XResult<UdsSocketId>::FromError(XErrC::InvalidArgument);
    }

    UdsSocketId client_fd = INVALID_XSOCKET_ID;
    fd_set read_set;
    int32_t ret = SelectRecvPoll(xsh->sock_id, read_set, timeout_ms);

    if ((ret > 0) && FD_ISSET(xsh->sock_id, &read_set)) {
        socklen_t cli_size;
        std::string clien_url;
          
        struct sockaddr_un cli;
        /* event from TCP server socket, new connection */
        cli_size = sizeof(cli);
        client_fd = ::accept(xsh->sock_id, (struct sockaddr *)&cli, &cli_size);  
        clien_url = cli.sun_path;

        if (client_fd >= 0) {
            printf("server(%s) got a connection from client(%s) with client id(%ld)\n", xsh->url.c_str(), clien_url.c_str(), client_fd);  
            UdsSocketHandler* clish = new UdsSocketHandler(client_fd);  
            return reinterpret_cast<UdsSocketId>(clish);
        } else {
            printf("accept() for server(%s) failed: %s\n", xsh->url.c_str(), strerror(errno));
        } 
    }        

    return XResult<UdsSocketId>::FromError(XErrC::TimeOut);
}

XResult<uint32_t> UdsSocketRecv(UdsSocketId sock_id, uint8_t* buf, uint32_t size, int32_t timeout_ms /*= core::WaitType::WaitForever*/) {
    UdsSocketHandler* xsh;
    if ((sock_id == INVALID_XSOCKET_ID) || !(xsh = reinterpret_cast<UdsSocketHandler*>(sock_id))) {
        return XResult<uint32_t>::FromError(XErrC::InvalidArgument);
    }

    return StreamRecv(xsh, buf, size, timeout_ms);
}

/** dst_fd: is valid only for udp protocol */
XResultCode UdsSocketSend(UdsSocketId sock_id, const uint8_t* buf, uint32_t size) {
    UdsSocketHandler* xsh;
    if ((sock_id == INVALID_XSOCKET_ID) || !(xsh = reinterpret_cast<UdsSocketHandler*>(sock_id)) || (!buf)) {
        return XResultCode::FromError(XErrC::InvalidArgument);
    }

    return StreamSend(xsh->sock_id, buf, size);
}

} //namespace shmipc::core
#endif /** defined HAS_LINUX_UDS */